using System;
using System.Collections.Generic;
using NFreeMarker.Core;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Template
{
    /**
     * Helper class for parsing setting values given with string.
     */

    internal class SettingStringParser
    {
        private readonly string _text;
        private int _p;
        private readonly int _ln;

        private SettingStringParser(string text)
        {
            _text = text;
            _p = 0;
            _ln = text.Length;
        }

        private List<string> ParseAsList()
        {
            var seq = new List<string>();
            while (true)
            {
                char c = SkipWs();
                if (c == ' ')
                {
                    break;
                }

                seq.Add(FetchStringValue());

                c = SkipWs();
                if (c == ' ')
                {
                    break;
                }

                if (c != ',')
                {
                    throw new ParseException(
                        "Expected \",\" or the end of text but "
                        + "found \"" + c + "\"", 0, 0);
                }

                _p++;
            }
            return seq;
        }

        private Dictionary<string, string> ParseAsImportList()
        {
            var map = new Dictionary<string, string>();
            while (true)
            {
                char c = SkipWs();
                if (c == ' ')
                {
                    break;
                }

                string lib = FetchStringValue();

                c = SkipWs();
                if (c == ' ')
                {
                    throw new ParseException(
                        "Unexpected end of text: expected \"as\"", 0, 0);
                }

                string s = FetchKeyword();
                if (!s.Equals("as", StringComparison.OrdinalIgnoreCase))
                {
                    throw new ParseException(
                        "Expected \"as\", but found " + StringUtil.JQuote(s), 0, 0);
                }

                c = SkipWs();
                if (c == ' ')
                {
                    throw new ParseException(
                        "Unexpected end of text: expected gate hash name", 0, 0);
                }

                string ns = FetchStringValue();

                map.Add(ns, lib);

                c = SkipWs();
                if (c == ' ') break;
                if (c != ',')
                    throw new ParseException(
                        "Expected \",\" or the end of text but "
                        + "found \"" + c + "\"", 0, 0);

                _p++;
            }
            return map;
        }

        private string FetchStringValue()
        {
            string w = FetchWord();
            if (w.StartsWith("'") || w.StartsWith("\""))
            {
                w = w.Substring(1, w.Length - 2);
            }
            return StringUtil.FtlStringLiteralDec(w);
        }

        private string FetchKeyword()
        {
            string w = FetchWord();
            if (w.StartsWith("'") || w.StartsWith("\""))
            {
                throw new ParseException(
                    "Keyword expected, but a string value found: " + w, 0, 0);
            }
            return w;
        }

        private char SkipWs()
        {
            while (_p < _ln)
            {
                char c = _text[_p];
                if (!Char.IsWhiteSpace(c))
                {
                    return c;
                }
                _p++;
            }
            return ' ';
        }

        private string FetchWord()
        {
            if (_p == _ln)
            {
                throw new ParseException("Unexpeced end of text", 0, 0);
            }

            char c = _text[_p];
            int b = _p;
            if (c == '\'' || c == '"')
            {
                bool escaped = false;
                char q = c;
                _p++;
                while (_p < _ln)
                {
                    c = _text[_p];
                    if (!escaped)
                    {
                        if (c == '\\')
                        {
                            escaped = true;
                        }
                        else if (c == q)
                        {
                            break;
                        }
                    }
                    else
                    {
                        escaped = false;
                    }
                    _p++;
                }
                if (_p == _ln)
                {
                    throw new ParseException("Missing " + q, 0, 0);
                }
                _p++;
                return _text.Substring(b, _p - b);
            }
            else
            {
                do
                {
                    c = _text[_p];
                    if (!(Char.IsLetterOrDigit(c)
                          || c == '/' || c == '\\' || c == '_'
                          || c == '.' || c == '-' || c == '!'
                          || c == '*' || c == '?'))
                    {
                        break;
                    }
                    _p++;
                } while (_p < _ln);
                if (b == _p)
                {
                    throw new ParseException("Unexpected character: " + c, 0, 0);
                }
                else
                {
                    return _text.Substring(b, _p - b);
                }
            }
        }
    }
}